#include <sys/types.h>          /* See NOTES */
#include <arpa/inet.h>
#include <netinet/tcp.h>
#include <stdlib.h>
#include <sys/timerfd.h>
#include <stdio.h>
#include <strings.h>
#include <sys/socket.h>

#include "php.h"
#include "server.h"
#include "select.h"
#include "silent.h"
#include "nginx.h"

#define SLT_BACKLOG	512
#define SLT_EVENT_CONN	0
#define SLT_EVENT_TASK 1

extern int silent_running;

void slt_server_task_free(void *data) {
	int i = 0;
	task_node_t *node = data;

	zval_ptr_dtor(&(node->cb));	
}


void slt_server_init(slt_server_t *serv) {
	serv->ticktime = 0;
	serv->sock = -1;
	serv->backend = 1;
	serv->max_conn = 10000;
	serv->schedid = 0;
	serv->wait_jobnum = 1;
	serv->job_num = 4;

	zend_llist_init(&(serv->task_list), sizeof(task_node_t), (void (*)(void *))slt_server_task_free, 0);
}

static int slt_server_getsock(int type) {
	int sock = -1;
	int _domain;
    int _type;

	switch (type) {
        case SLT_TCP_MODE: {
            _domain = AF_INET;
            _type = SOCK_STREAM;
            break;
        }
        default: {
            _domain = AF_INET;
            _type = SOCK_STREAM;
            break;
        }
    }
    sock = socket(_domain, _type, 0);

	return sock;
}

static int slt_server_listen(int type, char *host, int port, int backlog) {
	int sock;
	int ret;
	int option = 1;
	int tmp;
	struct sockaddr_in addr_in4;
	
	sock = slt_server_getsock(type);
    setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &option, sizeof(int));
	bzero(&addr_in4, sizeof(addr_in4));
	inet_pton(AF_INET, host, &(addr_in4.sin_addr));
	addr_in4.sin_port = htons(port);
	addr_in4.sin_family = AF_INET;
	ret = bind(sock, (struct sockaddr *)&addr_in4, sizeof (struct sockaddr_in));
	if (ret == -1) {
		return -1;
	}
	listen(sock, backlog);
	return sock;
}

void slt_server_create(slt_server_t *serv) {
	serv->sock = slt_server_listen(serv->mode, serv->host, (int)serv->port, SLT_BACKLOG);
	slt_factory_create(serv);
}

static int slt_server_emit_task(void *data) {
	task_node_t *node = data;
	time_t now;
	zval *retval = NULL;

    time(&now);
	TSRMLS_FETCH_FROM_CTX(slt_ctx);

    if (node->accept_time + node->delaytime <= now) {
        node->accept_time = now;
        if (call_user_function_ex(EG(function_table), NULL, node->cb, &retval, 0, NULL, 0, NULL TSRMLS_CC) == FAILURE) {
            php_error(E_WARNING, "call user api error");
        }

        zval_ptr_dtor(&retval);
		return node->one;
    }

	return 0;
}

static void slt_server_task(reactor_t *main_reactor, event_emit_node_t *emit_node) {
	slt_server_t *serv = main_reactor->ptr;
	int fd = emit_node->fd;
	uint64_t u;

	zend_llist_apply_with_del(&(serv->task_list), slt_server_emit_task);
	read(fd, &u, sizeof(uint64_t));
}

static void slt_server_accept(reactor_t *main_reactor, event_emit_node_t *emit_node) {
	int sock;
	int fd = emit_node->fd;
	int type = emit_node->type;
	char connection_ip[16];
	struct sockaddr_in remote_addr;
	slt_server_t *serv = main_reactor->ptr;
	reactor_t *nginx_reactor = NULL;
	socklen_t remote_addr_len = sizeof(struct sockaddr_in);

	sock = accept4(emit_node->fd, (struct sockaddr *)&remote_addr, &remote_addr_len, SOCK_NONBLOCK);
	inet_ntop(AF_INET, &(remote_addr.sin_addr), connection_ip, sizeof(connection_ip));
	serv->call_connection(connection_ip, main_reactor->id);
		
	if (serv->schedid >= serv->poll_thread_num) {
		serv->schedid = 0;
	}
	nginx_reactor = &(serv->pool[serv->schedid++].reactor);
	nginx_reactor->add(nginx_reactor, sock, SLT_NGINX_EVENT_ACCEPT);
}

static void slt_thread_pool_create(slt_server_t *serv) {
	int thread_num = serv->poll_thread_num;
	reactor_t *nginx_reactor = NULL;
	pthread_attr_t attr;
	pthread_t tid;
	int i = 0;

	serv->pool = ecalloc(thread_num, sizeof(slt_thread_pool_handler_t));
	for (; i<thread_num; i++) {
		nginx_reactor = &(serv->pool[i].reactor);
		nginx_reactor->ptr = serv;	
		nginx_reactor->id = i;
		pthread_create(&tid, NULL, slt_nginx_reactor_start, (void *)nginx_reactor);
		serv->pool[i].tid = tid;
	} 	
}

void slt_server_signal(int signo) {
	silent_running = 0;
}

void slt_server_task_with(void *data, void *args TSRMLS_DC) {
	task_node_t *task_node = data;
	slt_server_t *serv = args;

	if (serv->ticktime > task_node->delaytime) {
        serv->ticktime = task_node->delaytime;
    }
}

void slt_server_start(slt_server_t *serv) {
	reactor_t main_reactor;
	struct sigaction act;
	struct itimerspec new_value;
	struct timespec now;
	int fd;

	TSRMLS_FETCH_FROM_CTX(slt_ctx);
	if (serv->backend == 1) {
		if (-1 == daemon(0, 0)) {
			php_error(E_WARNING, "Failed to start backend mode");
		}
	}

	act.sa_handler = slt_server_signal;
	sigemptyset(&act.sa_mask);
	act.sa_flags = 0;

	main_reactor.id = 0;	
	main_reactor.ptr = serv;
	sigaction(SIGINT, &act, NULL);
	serv->factory.start(&(serv->factory));
	slt_thread_pool_create(serv);
	slt_select_reactor_create(&main_reactor);	

	main_reactor.set(&main_reactor, SLT_EVENT_CONN, slt_server_accept);
	main_reactor.set(&main_reactor, SLT_EVENT_TASK, slt_server_task);
	zend_llist_apply_with_argument(&(serv->task_list), slt_server_task_with, serv TSRMLS_CC);

	clock_gettime(CLOCK_REALTIME, &now);
	new_value.it_value.tv_sec = now.tv_sec + serv->ticktime;
	new_value.it_value.tv_nsec = now.tv_nsec;
	new_value.it_interval.tv_sec = serv->ticktime;
	new_value.it_interval.tv_nsec = 0;

	fd = timerfd_create(CLOCK_REALTIME, TFD_NONBLOCK);
	timerfd_settime(fd, TFD_TIMER_ABSTIME, &new_value, NULL);

	main_reactor.add(&main_reactor, fd, SLT_EVENT_TASK);
	main_reactor.add(&main_reactor, serv->sock, SLT_EVENT_CONN);
	main_reactor.wait(&main_reactor);
	main_reactor.free(&main_reactor);
}
